#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
using namespace std;

template <typename T>
class BlockQueue
{
public:
    bool IsFull()
    {
        return _block_queue.size() == _capacity;
    }
    bool IsEmpty()
    {
        return _block_queue.empty();
    }

public:
    BlockQueue(int capacity)
        : _capacity(capacity)
    {
        pthread_mutex_init(&(_mutex), nullptr);
        pthread_cond_init(&(_product_cond), nullptr);
        pthread_cond_init(&(_consume_cond), nullptr);
    }

    void EnQueue(const T &in)
    {
        pthread_mutex_lock(&(_mutex));
        while (IsFull())
        {
            productor_wait_num++;
            pthread_cond_wait(&(_product_cond), &_mutex);
            productor_wait_num--;
        }
        _block_queue.push(in);
        if(consumer_wait_num>0)
        {
            pthread_cond_signal(&(_consume_cond));
        }
        pthread_mutex_unlock(&(_mutex));
    }

    void DeQueue(T *out)
    {
        pthread_mutex_lock(&(_mutex));
        while (IsEmpty())
        {
            consumer_wait_num++;
            pthread_cond_wait(&(_consume_cond), &_mutex);
            consumer_wait_num--;
        }
        *out=_block_queue.front();
        _block_queue.pop();
        if(productor_wait_num>0)
        {
            pthread_cond_signal(&(_product_cond));
        }
        pthread_mutex_unlock(&(_mutex));
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&(_mutex));
        pthread_cond_destroy(&(_product_cond));
        pthread_cond_destroy(&(_consume_cond));
    }

private:
    queue<T> _block_queue;
    int _capacity;
    
    pthread_mutex_t _mutex;
    pthread_cond_t _product_cond;
    pthread_cond_t _consume_cond;

    int productor_wait_num=0;
    int consumer_wait_num=0;
};